﻿<script setup lang="ts" name="UploadResource">
import { FileApi } from '@/api/infra/file'
import { FileItem } from '../utils/type'
import { checkImgType, getBase64WithFile } from '../utils/helper'
import { buildUUID } from '@/utils/random'
import { cloneDeep } from 'lodash-es'
import { getActiveData, getTreeExpandNodeParents, returnSize, returnFileSize, checkSize } from '../utils/index'

const { error, success, warning } = useMessage()
const emit = defineEmits(['show', 'save', 'close'])

const props = defineProps({
  folderList: { type: Array as any },
  config: { type: Object },
  pid: { type: String, default: () => { return '' } },
  type: { type: String, default: () => { return '' } },
  attachment_id: { type: String, default: () => { return '' } }
})

const initInfo: any = ref({
  dialogVisible: false,
  fileType: 'all',
  accept: '',
  fileTypeList: [
    { label: '全部', value: 'all', disabled: false },
    { label: '图片', value: 'image', disabled: false },
    { label: '音视频', value: 'video', disabled: false },
    { label: '文件', value: 'file', disabled: false }
  ],
  helpTextList: [],
  folder_id: 0,
  treeList: [],
  isUploading: false,
  isAutoClose: false,
  uploadMessage: '',
  currentFolder: null,
  defaultExpandNodes: [],
  changeFolderId: null,
  fileListData: [],
  errorList: [],
  successList: [],
  successCount: 0,
  errorCount: 0,
  fullscreen: false,
  isChooseResource: false,
  fileTypeArr: ['7z', 'ai', 'cdr', 'class', 'css', 'csv', 'doc', 'dockerfile', 'docx', 'exe', 'gitignore', 'html', 'iso', 'jar', 'java', 'js', 'json', 'md', 'pdf', 'ppt', 'pptx', 'pr', 'psd', 'py', 'rar', 'sql', 'svg', 'txt', 'xls', 'xlsx', 'xml', 'yml', 'zip']
})

watch(
  () => props.folderList,
  (val) => {
    initInfo.value.treeList = val
    if (initInfo.value.changeFolderId) {
      initInfo.value.folder_id = initInfo.value.changeFolderId
      initInfo.value.currentFolder = getActiveData(initInfo.value.treeList, initInfo.value.changeFolderId)
      initInfo.value.defaultExpandNodes = getTreeExpandNodeParents(initInfo.value.treeList, initInfo.value.currentFolder.folder_id)
      initInfo.value.defaultExpandNodes.push(initInfo.value.currentFolder.id)
    }
  },
  {
    deep: true,
    immediate: true
  }
)

// 更新文件夹选中状态
const updateTreeSelected = (v) => {
  initInfo.value.changeFolderId = v.id
}

// 打开弹窗
const showDialog = (v) => {
  initInfo.value.dialogVisible = true
  initInfo.value.currentFolder = v || initInfo.value.treeList[0]
  initInfo.value.folder_id = v ? v.id : initInfo.value.treeList[0].id
  initInfo.value.defaultExpandNodes = getTreeExpandNodeParents(initInfo.value.treeList, initInfo.value.currentFolder.folder_id)
  initInfo.value.defaultExpandNodes.push(initInfo.value.currentFolder.id)
  initInfo.value.fileType = props.config?.type
  acceptAndHelpText(props.config?.type)
}

const fileTypeChangeHandler = (v) => {
  console.log(v, '切换资源类型')
  acceptAndHelpText(v)
}

const acceptAndHelpText = (v) => {
  initInfo.value.helpTextList = []
  let acceptStr = ''
  if (!v) {
    v = "all"
  }
  if (v === 'all') {
    const acceptArr: any = []
    initInfo.value.fileTypeList.forEach(item => {
      if (item.value !== 'all') {
        initInfo.value.helpTextList.push(helpText(item.value))
        acceptArr.push(props.config?.accept[item.value])
      }
    })
    acceptStr = acceptArr.join(',')
  } else {
    initInfo.value.helpTextList.push(helpText(v))
    acceptStr = props.config?.accept[v]
  }
  const acceptArray = acceptStr.split(',')
  const acceptRes: any = []
  acceptArray.forEach(item => {
    if (!item.includes('.')) {
      acceptRes.push('.' + item)
    } else {
      acceptRes.push(item)
    }
  })
  initInfo.value.accept = acceptRes.join(',')
  console.log(initInfo.value.accept, '资源类型')
}

const isChooseResource = () => {
  initInfo.value.isChooseResource = true
}

// 关闭弹窗
const closeDialog = () => {
  if (initInfo.value.isUploading) {
    warning('请等待文件上传结束后操作')
    return false
  } else {
    initInfo.value.dialogVisible = false
    initInfo.value.fileListData = []
    initInfo.value.successList = []
    initInfo.value.errorList = []
    initInfo.value.successCount = 0
    initInfo.value.errorCount = 0
    initInfo.value.fullscreen = false
    initInfo.value.isAutoClose = false
    initInfo.value.uploadMessage = ''
    initInfo.value.accept = ''
  }
  emit('close')
}

// 子组件将方法暴露给父组件
defineExpose({ showDialog, updateTreeSelected, isChooseResource })

// 提示文本组装
const helpText = (type) => {
  const helpTexts: any = []
  let typeText = ''
  if (type === 'image') {
    typeText = '图片文件'
  } else if (type === 'video') {
    typeText = '媒体文件'
  } else {
    typeText = '文档文件'
  }
  helpTexts.push('支持上传' + props.config?.accept[type] + '格式' + typeText)
  helpTexts.push(props.config?.multiple ? ('单个文件不超过' + props.config?.fileSize[type]) : ('大小不超过' + props.config?.fileSize[type]))
  if (props.config?.multiple && props.config?.maxNumber > 1) {
    helpTexts.push('最多只能上传' + props.config?.maxNumber + '个文件')
  }
  return helpTexts.join('，')
}

// 选择文件后上传前的操作
const beforeUpload = async (file: File) => {
  console.log(file, '文件上传')
  const { size, name, type } = file
  let fileType = ''
  if (type.includes('image')) {
    fileType = 'image'
  } else if (type.includes('video')) {
    fileType = 'video'
  } else if (type.includes('audio')) {
    fileType = 'audio'
  } else {
    const nameArr = name.split('.')
    fileType = nameArr[nameArr.length - 1]
  }
  let checkSizes
  if (fileType === 'audio') {
    checkSizes = await checkSize(file, false)
  } else if (fileType === 'video') {
    checkSizes = await checkSize(file, true)
  }
  console.log(checkSizes, '媒体数据')
  let maxSize = 0
  if (fileType === 'image') {
    maxSize = props.config?.maxSize.image * 1024
  } else if (fileType === 'video' || fileType === 'audio') {
    maxSize = props.config?.maxSize.video * 1024
  } else {
    maxSize = props.config?.maxSize.file * 1024
  }
  // 设置最大值，则判断
  if (maxSize) {
    let typeText = ''
    if (type === 'image') {
      typeText = '图片文件'
    } else if (type === 'video') {
      typeText = '媒体文件'
    } else {
      typeText = '文档文件'
    }
    if (maxSize > 0 && file.size > maxSize) {
      error('只能上传不超过' + returnSize(maxSize) + '的' + typeText + '!')
      return false
    }
  }
  const fileSize = returnFileSize(size)
  const commonItem = {
    uuid: buildUUID(),
    file,
    size: fileSize,
    name,
    percent: 0,
    status: 1, // 1:等待上传，2：正在上传，3：上传成功，4：上传失败, 5: 文件重复
    type: fileType,
    folder: initInfo.value.currentFolder.title,
    parameter: checkSizes
  }
  // 生成图片缩略图
  if (checkImgType(file)) {
    // beforeUpload，如果异步会调用自带上传方法
    // file.thumbUrl = await getBase64(file);
    getBase64WithFile(file).then(({ result: thumbUrl }) => {
      const fileList = cloneDeep(initInfo.value.fileListData)
      fileList.push({
        thumbUrl,
        ...commonItem
      })
      initInfo.value.fileListData = fileList
      setUploadMessage()
    })
  } else {
    const newFileList = [...initInfo.value.fileListData, commonItem]
    initInfo.value.fileListData = newFileList
    setUploadMessage()
  }
  return false
}

// 上传说明文字显示
const setUploadMessage = () => {
  if (initInfo.value.successCount > 0) {
    initInfo.value.uploadMessage = '共有' + initInfo.value.successCount + '个文件上传成功'
    if (initInfo.value.errorCount > 0) {
      initInfo.value.uploadMessage += '，' + initInfo.value.errorCount + '个文件上传失败。'
    } else {
      initInfo.value.uploadMessage += '。'
    }
  } else {
    if (initInfo.value.errorCount > 0) {
      initInfo.value.uploadMessage = '共有' + initInfo.value.errorCount + '个文件上传失败。'
    } else {
      initInfo.value.uploadMessage = '共有' + initInfo.value.fileListData.length + '个文件等待上传。'
    }
  }
}

// 开始上传按钮事件
const handleUpload = async () => {
  const maxNumber = props.config?.maxNumber
  if (maxNumber) {
    if (initInfo.value.fileListData.length > maxNumber) {
      return warning('最多只能上传' + maxNumber + '个文件')
    }
  }
  try {
    initInfo.value.errorList = []
    initInfo.value.errorCount = 0
    initInfo.value.isUploading = true
    // 只上传不是成功状态的
    const uploadFileList = initInfo.value.fileListData.filter((item) => item.status !== 3) || []
    const data = await Promise.all(
      uploadFileList.map((item) => {
        return uploadByItem(item)
      })
    )
    initInfo.value.isUploading = false
    data.forEach((item) => {
      if (item.success) {
        initInfo.value.successList.push(item)
        setUploadMessage()
      }
    })
    // 生产环境:抛出错误
    initInfo.value.errorList = data.filter((item: any) => !item.success)
    setUploadMessage()
    // if (errorList.length > 0) throw errorList
    if (initInfo.value.fileListData.length === initInfo.value.successList.length && initInfo.value.isAutoClose) {
      success('已全部上传成功！')
      handleOk()
    }
  } catch (e) {
    initInfo.value.isUploading = false
    throw e
  }
}

// 单个文件上传调用上传接口
const uploadByItem = async (item) => {
  try {
    item.status = 2
    const resBody = new FormData()
    resBody.append('file', item.file)
    resBody.append('folder_id', initInfo.value.folder_id + '')
    resBody.append('pid', props.pid)
    resBody.append('type', props.type)
    resBody.append('attachment_id', props.attachment_id)
    resBody.append('parameter', item.parameter ? JSON.stringify(item.parameter) : '')
    const result: any = await FileApi.uploadProgress(resBody, (progressEvent: ProgressEvent) => {
      const complete = ((progressEvent.loaded / progressEvent.total) * 100) | 0
      item.percent = complete
    })
    if (result.code === 200) {
      item.status = 3
      item.responseData = result.data
      initInfo.value.successCount += 1
      setUploadMessage()
      return {
        success: true,
        error: null
      }
    } else if (result.code === 201) {
      item.status = 5
      item.responseData = result.data
      initInfo.value.errorCount += 1
      setUploadMessage()
      return {
        success: false,
        error: result.msg
      }
    } else {
      item.status = 4
      initInfo.value.errorCount += 1
      setUploadMessage()
      return {
        success: false,
        error: result.msg
      }
    }
  } catch (e) {
    item.status = 4
    initInfo.value.errorCount += 1
    setUploadMessage()
    return {
      success: false,
      error: e
    }
  }
}

// 上传弹窗的删除事件
const handleFileRemove = (record: FileItem) => {
  const fileList = cloneDeep(initInfo.value.fileListData)
  const index = fileList.findIndex((item) => item.uuid === record.uuid)
  index !== -1 && fileList.splice(index, 1)
  initInfo.value.fileListData = fileList
  if (record.status === 4 || record.status === 5) {
    initInfo.value.errorList.splice(0, 1)
  }
  setUploadMessage()
  console.log(initInfo.value.fileListData)
}

// 上传弹窗的保存按钮点击事件，保存上传成功的数据
const handleOk = () => {
  if (initInfo.value.fileListData.length > 0) {
    const maxNumber = props.config?.maxNumber
    if (maxNumber) {
      if (initInfo.value.successList.length > maxNumber) {
        return warning('最多只能上传' + maxNumber + '个文件')
      }
    }
    if (initInfo.value.isUploading) {
      return warning('请等待文件上传结束后操作')
    }
    const fileList: any = []

    for (const item of initInfo.value.fileListData) {
      const { status, responseData } = item
      if ((status === 3 || status === 5) && responseData && responseData.url) {
        fileList.push({ name: responseData.name, url: responseData.url })
      }
    }
    // 存在一个上传成功的即可保存
    if (fileList.length <= 0) {
      return warning('请等待文件上传结束后操作')
    }
    emit('save', fileList)
  }
  closeDialog()
}

// 最大化点击事件
const maxSizeHandle = () => {
  initInfo.value.fullscreen = !initInfo.value.fullscreen
}

// 文件夹树结构查询过滤
const filterNode = (value, data) => {
  if (!value) return true
  return data.title.includes(value)
}

// 显示新建文件夹弹窗
const showFolderAdd = () => {
  initInfo.value.changeFolderId = null
  emit('show', getActiveData(initInfo.value.treeList, initInfo.value.folder_id))
}
</script>

<template>
  <ElDialog v-model="initInfo.dialogVisible" width="960px" append-to-body destroy-on-close :fullscreen="initInfo.fullscreen" :show-close="false" :before-close="closeDialog">
    <template #header>
      <div class="modal-header">
        <div class="header-title">
          {{ initInfo.isUploading ? ('上传中（' + initInfo.successCount + '/' +
    initInfo.fileListData.length + '）') : '上传' }}
        </div>
        <ElTooltip :content="initInfo.fullscreen ? '还原' : '最大化'" placement="bottom">
          <ElButton size="small" @click="maxSizeHandle"><SvgIcon :icon="initInfo.fullscreen ? 'bi:fullscreen-exit' : 'bi:fullscreen'" /></ElButton>
        </ElTooltip>
        <ElTooltip content="关闭" placement="bottom">
          <ElButton size="small" @click="closeDialog"><SvgIcon icon="ep:close" /></ElButton>
        </ElTooltip>
      </div>
    </template>
    <div v-if="initInfo.fileListData.length === 0" :class="['init-upload', initInfo.fullscreen ? 'is-fullscreen' : '']">
      <div class="upload-header">
        <span>上传</span>
        <ElSelect v-model="initInfo.fileType" class="file-type" :disabled="!props.config?.isAllowChangeType" @change="fileTypeChangeHandler">
          <ElOption v-for="item in initInfo.fileTypeList" :key="item.value" :label="item.label" :value="item.value" :disabled="item.disabled" />
        </ElSelect>
        <span>类型资源</span>
        <span>到</span>
        <ElTreeSelect
          v-model="initInfo.folder_id"
          class="tree-level"
          node-key="id"
          :props="{ label: 'title' }"
          :data="initInfo.treeList"
          check-strictly
          :render-after-expand="false"
          filterable
          auto-expand-parent
          :filter-node-method="filterNode"
          :expand-on-click-node="false"
          :check-on-click-node="true"
          :default-expanded-keys="initInfo.defaultExpandNodes">
          <template #default="{ node, data }">
            <span class="item-tree-node">
              <span>{{ node.label }}</span>
              <template v-for="item in data.level">
                <span v-if="!data.hideLine.includes(item)" :key="item.value" :class="['item-tree-node-line-vertical', data.isLast && item == data.level ? 'is-last' : '', data.isFirst && data.level === 1 ? 'is-first' : '']" :style="{ left: 8 + 18 * (item - 1) + 12 + 'px' }" />
              </template>
              <span class="item-tree-node-line-horizontal" :style="{ left: 8 + 18 * (data.level - 1) + 12 + 'px' }" />
            </span>
          </template>
        </ElTreeSelect>
        <ElButton v-if="!initInfo.isChooseResource" type="primary" link @click="showFolderAdd">新建文件夹</ElButton>
      </div>
      <ElUpload :accept="initInfo.accept" :multiple="props.config?.multiple" :before-upload="beforeUpload" drag>
        <div class="upload-image">
          <SvgIcon icon="ri:upload-cloud-line" :size="initInfo.fullscreen ? 240 : 120" />
        </div>
        <div class="upload-remark">
          将资源拖放到此处上传，或点击
          <span>上传</span>
        </div>
        <div v-for="(item, index) in initInfo.helpTextList" :key="index" class="upload-rule">{{ item }}</div>
        <div v-if="props.config?.multiple" class="upload-remark"><strong>支持选择多个资源上传</strong></div>
      </ElUpload>
    </div>
    <div v-else :class="['list-upload', initInfo.fullscreen ? 'is-fullscreen' : '']">
      <ElAlert :title="initInfo.uploadMessage" :type="initInfo.successList.length === initInfo.fileListData.length ? 'success' : 'info'" :closable="false" center />
      <div class="table-wrap">
        <div class="table-abs">
          <ElTable border :data="initInfo.fileListData" :max-height="initInfo.fullscreen ? '' : 400">
            <ElTableColumn prop="thumbUrl" label="文件" width="90" align="center">
              <template #default="scope">
                <div class="image-wrap">
                  <SvgIcon v-if="scope.row.isFolder" icon="ci:folder" size="32" />
                  <template v-else>
                    <ElImage v-if="scope.row.type === 'image'" :src="scope.row.thumbUrl" fit="fill" lazy />
                    <SvgIcon v-else-if="scope.row.type === 'video'" icon="ci:filetype-video" size="32" />
                    <SvgIcon v-else-if="scope.row.type === 'audio'" icon="ci:filetype-audio" size="32" />
                    <template v-else>
                      <SvgIcon v-if="initInfo.fileTypeArr.includes(scope.row.type)" :icon="'ci:filetype-' + scope.row.type" size="32" />
                      <SvgIcon v-else icon="ci:filetype-unknown" size="32" />
                    </template>
                  </template>
                </div>
              </template>
            </ElTableColumn>
            <ElTableColumn prop="name" label="名称">
              <template #default="scope">
                <div class="file-name">
                  <p>{{ scope.row.name }}</p>
                  <ElProgress :percentage="scope.row.percent" :status="scope.row.status === 3 ? 'success' : scope.row.status === 4 ? 'exception' : scope.row.status === 5 ? 'warning' : ''" />
                </div>
              </template>
            </ElTableColumn>
            <ElTableColumn prop="size" label="大小" width="120" align="center" />
            <ElTableColumn prop="folder" label="文件夹" width="120" align="center" />
            <ElTableColumn prop="status" width="160" label="状态" align="center">
              <template #default="scope">
                <div class="file-status">
                  <ElTag v-if="scope.row.status === 1" type="info">等待上传</ElTag>
                  <ElTag v-if="scope.row.status === 2">正在上传</ElTag>
                  <ElTag v-if="scope.row.status === 3" type="success">上传成功</ElTag>
                  <ElTag v-if="scope.row.status === 4" type="danger">上传失败</ElTag>
                  <ElTag v-if="scope.row.status === 5" type="warning">文件重复</ElTag>
                </div>
              </template>
            </ElTableColumn>
            <ElTableColumn prop="operate" label="操作" width="100" align="center">
              <template #default="scope">
                <div class="file-operate">
                  <ElLink v-if="scope.row.status === 1 || scope.row.status === 4 || scope.row.status === 5" :underline="false" type="danger" @click="handleFileRemove(scope.row)">删除</ElLink>
                </div>
              </template>
            </ElTableColumn>
          </ElTable>
        </div>
      </div>
    </div>
    <template #footer>
      <span class="modal-footer">
        <ElCheckbox v-if="initInfo.fileListData.length > 0" v-model="initInfo.isAutoClose">上传完成自动关闭窗口</ElCheckbox>
        <ElButton @click="closeDialog">取消</ElButton>
        <ElUpload v-if="initInfo.fileListData.length > 0" :accept="initInfo.accept" :multiple="props.config?.multiple" :before-upload="beforeUpload">
          <ElButton>添加更多资源</ElButton>
        </ElUpload>
        <ElButton v-if="initInfo.fileListData.length > 0 && initInfo.fileListData.length !== initInfo.successList.length" type="primary" :disabled="initInfo.isUploading" @click="handleUpload">
          {{ initInfo.errorList.length > 0 ? '重新上传失败文件' : '开始上传' }}
        </ElButton>
        <ElButton type="primary" :disabled="initInfo.isUploading" @click="handleOk">确定</ElButton>
      </span>
    </template>
  </ElDialog>
</template>

<style scoped lang="scss">
.modal-header {
  display: flex;
  padding: 15px var(--el-space);
  margin: -15px calc(0px - var(--el-space));

  .header-title {
    flex: 1;
    height: 24px;
    line-height: 24px;
  }

  .el-button {
    margin-left: 0;
    background: none !important;
    border: none !important;

    &:hover,
    &:focus {
      background: none;
    }
  }
}

.modal-footer {
  display: flex;
  justify-content: flex-end;
  overflow: hidden;

  .el-button+div {
    margin: 0 12px;
  }

  .el-checkbox {
    margin-right: auto;
  }
}

.image-wrap {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  margin: 0 auto;
  border: 1px solid var(--el-border-color);

  .el-image {
    max-width: 40px;
    max-height: 40px;

    :deep(.el-image__inner) {
      max-width: 100%;
      max-height: 38px;
    }
  }

  &.folder {
    border: none;

    .el-image {
      max-width: 32px;
      max-height: 32px;

      :deep(.el-image__inner) {
        max-width: 32px;
        max-height: 32px;
      }
    }
  }
}

.file-name {
  p {
    .el-icon {
      font-size: 16px !important;
      color: var(--el-color-danger);
    }
  }
}

.file-operate {
  .el-link {
    margin: 3px;
  }
}

.el-alert--success,
.el-alert--info {
  margin-bottom: var(--el-space);

  &.is-light {
    border: 1px solid var(--el-alert-bg-color);
  }
}

.upload-header {
  margin-bottom: var(--el-space);

  .file-type {
    width: 90px;
  }

  .el-select {
    margin: 0 6px;
  }
}

.is-fullscreen {
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
  padding: var(--el-space);

  &.init-upload {
    .upload-header+div {
      display: flex;
      flex: 1;

      :deep(.el-upload.is-drag) {
        display: flex;
        flex: 1;

        .el-upload-dragger {
          flex: 1;
          height: 100%;
        }
      }
    }
  }
}

.init-upload {
  :deep(.el-upload) {

    &:active,
    &:focus {

      .upload-remark,
      .upload-rule {
        color: var(--el-text-color-regular) !important;
      }
    }

    .el-upload-dragger {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      height: 400px;
      padding: var(--el-space);

      &:hover {
        background-color: var(--el-fill-color-extra-light);
        border-color: var(--el-border-color)
      }

      &:active,
      &:focus {

        .upload-remark,
        .upload-rule {
          color: var(--el-text-color-regular) !important;
        }
      }
    }
  }

  :deep(.el-upload-list) {
    margin-top: 0;
  }

  .upload-image {
    .el-icon {
      color: var(--el-color-primary);
    }
  }

  .upload-remark {
    margin: 15px 0;
    font-size: 16px;

    span {
      color: var(--el-color-primary);
    }
  }

  .upload-rule {
    margin: 3px 0;
  }
}

.list-upload {
  display: flex;
  flex-direction: column;

  .table-wrap {
    position: relative;
    flex: 1;

    .table-abs {
      // position: absolute;
      // top: 0;
      // left: 0;
      // width: 100%;
      // height: 100%;

      .el-table {
        width: 100%;
      }
    }
  }

  &.is-fullscreen {
    .table-wrap {
      .table-abs {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;

        .el-table {
          width: 100%;
          height: 100%;
        }
      }
    }
  }
}
</style>
